// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: sys_env.hpp 229 2008-09-15 13:39:09Z heavyzheng $
//

#ifndef ZZHENG_UTILITY_SYS_ENV_HPP_20080703__
#define ZZHENG_UTILITY_SYS_ENV_HPP_20080703__

#include <zzheng/config.hpp>

#if defined(ZZHENG_WIN32)
#  include <zzheng/utility/sys_env_win32.hpp>
#else
#  include <zzheng/utility/sys_env_dummy.hpp>
#endif // ZZHENG_WIN32


#ifdef ZZHENG_MSVC
#  pragma warning(push)
#  pragma warning(disable: 4512 4702)
#endif // ZZHENG_MSVC

#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(pop)
#endif // ZZHENG_MSVC

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>

namespace zzheng {
namespace sys {

    //! \addtogroup utility zzheng.utility
    //! \{

    #ifndef DOXYGEN_SHOULD_SKIP_THIS

    namespace detail {

        //! Enumeration of token flags indicating if a token is found or not. Note the value of the
        //! token-found flag is less than that of the token-not-found flag. This is to keep the
        //! token indexer comparison consistent: the first token is the minimal token among all
        //! found tokens.
        enum token_flag {
            token_found     = 1, //!< The token is found.
            token_not_found = 2, //!< The token is not found.
        };

        //! Define the token indexer type as a 3-valued tuple.
        typedef boost::tuple<
            token_flag,             //!< Flag indicating if the token is found.
            std::string::size_type, //!< The begin index of the token.
            std::string::size_type  //!< The end index (one-after-last) of the token.
        > token_indexer;

        //! Returns a reference to the token's 1st element, which is the flag indicating if the
        //! token is found. We need this helper function because boost::tuple's get() member
        //! function is not supported by MSVC.
        inline token_flag& get_flag(token_indexer& indexer) {
            return boost::tuples::get<0>(indexer);
        }

        //! Returns a reference to the token's 2nd element, which is the begin index.
        inline std::string::size_type& get_begin_index(token_indexer& indexer) {
            return boost::tuples::get<1>(indexer);
        }

        //! Returns a reference to the token's 3rd element, which is the end index. We need this
        //! helper function because boost::tuple's get() member function is not supported by MSVC.
        inline std::string::size_type& get_end_index(token_indexer& indexer) {
            return boost::tuples::get<2>(indexer);
        }

        //! Finds the first occurrence of a token.
        //! \param str    the string in which this function searches.
        //! \param index  the starting index of the searching.
        //! \param begin  the string indicating the beginning of the token.
        //! \param end    the string indicating the end of the token.
        //! \return the token tuple found. If the token is not found, the returned token tuple has
        //!         its token flag set to 'token_not_found'.
        inline token_indexer find_token(const std::string& str,
                                        std::string::size_type index,
                                        const std::string& begin,
                                        const std::string& end) {
            token_indexer indexer = boost::make_tuple(token_not_found,    // token is not found
                                                      std::string::npos,  // begin index is none
                                                      std::string::npos); // end index is none
            get_begin_index(indexer) = str.find(begin, index);
            if (get_begin_index(indexer) != std::string::npos) {
                get_end_index(indexer) = str.find(end, get_begin_index(indexer) + begin.size());
                if (get_end_index(indexer) != std::string::npos) {
                    get_end_index(indexer) += end.size();
                    get_flag(indexer) = token_found;
                }
            }
            return indexer;
        }

        //! Finds the first occurrence of the system variable name(s) token.
        //! \param str  the string in which this function searches.
        //! \param index  the starting index of the searching.
        //! \return the first system variable name(s) token tuple found. If no token is found, the
        //!         returned token tuple has its token flag set to 'token_not_found_'.
        inline token_indexer find_token(const std::string& str, std::string::size_type index) {
            token_indexer indexer_1 = find_token(str, index, "%" , "%"); // find %NAME%
            token_indexer indexer_2 = find_token(str, index, "${", "}"); // find ${NAME}
            token_indexer indexer_3 = find_token(str, index, "$(", ")"); // find $(NAME)
            return (std::min)((std::min)(indexer_1, indexer_2), indexer_3);
        }

        struct is_slash: public std::unary_function<std::string::value_type, bool> {
            bool operator()(const std::string::value_type& c) const {
                return (c == '/' || c == '\\');
            }
        }; // struct is_slash

    } // namespace zzheng::sys::detail

    #endif // !DOXYGEN_SHOULD_SKIP_THIS

    ////////////////////////////////////////////////////////////////////////////////////////////////

    //! Returns the platform-dependent path separator as a character. The path separator character
    //! is used to separate paths in the PATH system variable.
    //! \return the platform-dependent path separator as a character.
    inline char path_sep_char() {
        return detail::path_sep_char();
    }

    //! Returns the platform-dependent path separator as a string.
    //! \return the platform-dependent path separator as a string.
    inline std::string path_sep_str() {
        return std::string(1, detail::path_sep_char());
    }

    //! Returns the platform-dependent file separator as a character. The file separator character
    //! is used to separate file names and directory names in a path.
    //! \return the platform-dependent file separator as a character.
    inline char file_sep_char() {
        return detail::file_sep_char();
    }

    //! Returns the platform-dependent file separator as a string.
    //! \return the platform-dependent file separator as a string.
    inline std::string file_sep_str() {
        return std::string(1, detail::file_sep_char());
    }

    //! Returns the system variable value by name. System variable names may be grouped using '|'.
    //! The following formats for system variable names are supported:
    //!   NAME or %NAME% or ${NAME} or $(NAME)
    //! \param name_group  the name or a group of names of the system variable.
    //! \return the value of the system variable.
    inline std::string get_sys_var(const std::string& name_group) {

        // Strip blank spaces and special characters off the names.
        std::string stripped = boost::trim_copy(name_group);
        if (boost::starts_with(stripped, "%") && boost::ends_with(stripped, "%")) {
            stripped = stripped.substr(1, stripped.size() - 2);
        } else if (boost::starts_with(stripped, "${") && boost::ends_with(stripped, "}")) {
            stripped = stripped.substr(2, stripped.size() - 3);
        } else if (boost::starts_with(stripped, "$(") && boost::ends_with(stripped, ")")) {
            stripped = stripped.substr(2, stripped.size() - 3);
        }

        // Split the names into tokens, each of which is a system variable name.
        std::vector<std::string> names;
        boost::split(names, stripped, boost::is_any_of("| "));

        // Search the first existing system variable and return its value.
        std::string value;
        for (std::vector<std::string>::iterator i = names.begin(); i != names.end(); ++i) {
            value = detail::get_sys_var(*i);
            if (!value.empty()) {
                break; // end the loop if the variable value is found.
            }
        }
        return value;
    }

    //! Sets (and overwrites if necessary) the system variable.
    //! \param name   the name of the system variable to set.
    //! \param value  the value of the system variable to set.
    //! \return true if the system variable is set successfully, false otherwise.
    inline bool set_sys_var(const std::string& name, const std::string& value) {
        return detail::set_sys_var(name, value);
    }

    //! Expands the system variables in the specified string. This function searches the system
    //! variable names in the string, and replaces each name by its value. If the system variable
    //! cannot be found, the name is unchanged. System variable names may be grouped using '|'.
    //! In such case, the first system variable found will replace the name group.
    //!
    //! The following formats for system variable names are supported:
    //!   %NAME1% or %NAME1|NAME2|NAME3%
    //!   ${NAME1} or ${NAME1|NAME2|NAME3}
    //!   $(NAME1) or $(NAME1|NAME2|NAME3)
    //!
    //! \param str  the string containing system variables to expand.
    //! \return the expanded string.
    inline std::string expand_sys_var(const std::string& str) {
        std::string expanded;
        for (std::string::size_type i = 0; i < str.size(); ) {
            detail::token_indexer indexer = detail::find_token(str, i);
            if (detail::get_flag(indexer) != detail::token_found) {
                // Token is not found: append all the rest characters to the expanded string.
                expanded.append(str.substr(i));
                i = str.size();
            } else {
                // Token is found: try to replace the name(s) by value.
                assert(detail::get_begin_index(indexer) >= i);
                assert(detail::get_end_index(indexer) > detail::get_begin_index(indexer));
                expanded.append(str.substr(i, detail::get_begin_index(indexer) - i));
                std::string names = str.substr(detail::get_begin_index(indexer),
                                               detail::get_end_index(indexer)
                                             - detail::get_begin_index(indexer));
                std::string value = get_sys_var(names);
                if (value.empty()) {
                    // System variable value is not found.
                    expanded.append(str.substr(detail::get_begin_index(indexer), 1));
                    i = detail::get_begin_index(indexer) + 1;
                } else {
                    // System variable value is found.
                    expanded.append(value);
                    i = detail::get_end_index(indexer);
                }
            }
        }
        return expanded;
    }

    inline std::string join_path(const std::string& dir, const std::string& file) {
        return ( boost::trim_right_copy_if(dir, detail::is_slash())
               + file_sep_str()
               + boost::trim_left_copy_if(file, detail::is_slash()));
    }

    inline std::string join_path(const std::string& dir1,
                                 const std::string& dir2,
                                 const std::string& file) {
        return join_path( join_path(dir1, dir2), file );
    }

    inline std::string join_path(const std::string& dir1,
                                 const std::string& dir2,
                                 const std::string& dir3,
                                 const std::string& file) {
        return join_path( join_path(dir1, dir2, dir3), file );
    }

    //! \} end group utility

} // namespace zzheng::sys
} // namespace zzheng

#endif // ZZHENG_UTILITY_SYS_ENV_HPP_20080703__


